package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"os"
	"strconv"
)

var ibdFilePath = flag.String("ibd-file-path", "", "mandatory. innodb file path with suffix of .ibd")

const PageSize = 16384
const FileHeaderSize = 38
const PageHeaderSize = 56

var minLsn int64
var maxLsn int64
var PageMaps map[int]string
var pages []Page

// e.g.  ./innodbReader -ibd-file-path xxx.ibd

func main() {

	flag.Parse()

	if *ibdFilePath == "" {
		fmt.Println("please input ibd-file-path")
		os.Exit(0)
	}

	PageMaps = make(map[int]string)
	PageMaps[0] = "ALLOCATED"
	PageMaps[2] = "UNDO_LOG"
	PageMaps[3] = "INODE"
	PageMaps[4] = "IBUF_FREE_LIST"
	PageMaps[5] = "IBUF_BITMAP"
	PageMaps[6] = "SYS"
	PageMaps[7] = "TRX_SYS"
	PageMaps[8] = "FSP_HDR"
	PageMaps[9] = "XDES"
	PageMaps[17853] = "SDI"
	PageMaps[17855] = "INDEX"

	PageMap(*ibdFilePath)
	OpPageMap()

	fmt.Println("It's Okkkk!")
}

func PageMap(filePath string) {
	file, errOpen := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
	if errOpen != nil {
		fmt.Println("file open failed: ", errOpen.Error())
	}
	defer func() {
		if file != nil {
			_ = file.Close()
		}
	}()
	if file != nil {
		fs, errFS := file.Stat()
		if errFS != nil {
			fmt.Println("file open failed: ", errFS.Error())
		}
		if fs != nil {
			if !checkFile(fs.Size(), filePath) {
				os.Exit(0)
			}
			var readPos int64 = 0
			for {
				if readPos == fs.Size() {
					break
				}
				_, errFSeek := file.Seek(readPos, io.SeekStart)
				if errFSeek != nil {
					fmt.Println("file open failed: ", errFSeek)
				}
				b := make([]byte, FileHeaderSize+PageHeaderSize)
				_, errRD := file.Read(b)

				if errRD != nil {
					fmt.Println("file open failed: ", errRD)
				}

				fileHeader := readFileHeader(b[0:38])
				var page Page
				page.PageType = PageMaps[int(fileHeader.PageType)]
				page.PageLsn = fileHeader.PageLsn
				page.PageOffset = fileHeader.PageOffset

				if PageMaps[int(fileHeader.PageType)] == "INDEX" {

					pageHeader := readPageHeader(b[38:94])
					page.PageLevel = pageHeader.PageLevel
				}
				pages = append(pages, page)
				readPos += PageSize
			}
		}
	}
}

func OpPageMap() {
    //应该在解析阶段实现最大LSN、最小LSN的设置，这里有点重复，放在PageMap方法中比较合适，2023.04.20
	maxLsn = pages[0].PageLsn
	minLsn = pages[0].PageLsn
	for i := 0; i < len(pages); i++ {
		if pages[i].PageType != "ALLOCATED" {
			if maxLsn < pages[i].PageLsn {
				maxLsn = pages[i].PageLsn
			}
			if minLsn > pages[i].PageLsn {
				minLsn = pages[i].PageLsn
			}
		}
	}

	html := ``
	html += ReadHtml("html/templates/pagemap_up.html")
	lsnRange := maxLsn - minLsn
	var diffLsn int64
	if lsnRange > 11 {
		diffLsn = int64(math.Floor(float64(lsnRange / 11)))
	}
	for i := 0; i < len(pages); i++ {
		html += `<div class="box `
		if pages[i].PageType != "ALLOCATED" {
			if pages[i].PageLsn >= minLsn && pages[i].PageLsn < (minLsn+diffLsn) {
				html += `cold5` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+diffLsn) && pages[i].PageLsn < (minLsn+(2*diffLsn)) {
				html += `cold4` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+(2*diffLsn)) && pages[i].PageLsn < (minLsn+(3*diffLsn)) {
				html += `cold3` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+(3*diffLsn)) && pages[i].PageLsn < (minLsn+(4*diffLsn)) {
				html += `cold2` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+(4*diffLsn)) && pages[i].PageLsn < (minLsn+(5*diffLsn)) {
				html += `cold1` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+(5*diffLsn)) && pages[i].PageLsn < (minLsn+(6*diffLsn)) {
				html += `gray0` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+(6*diffLsn)) && pages[i].PageLsn < (minLsn+(7*diffLsn)) {
				html += `warm1` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+(7*diffLsn)) && pages[i].PageLsn < (minLsn+(8*diffLsn)) {
				html += `warm2` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+(8*diffLsn)) && pages[i].PageLsn < (minLsn+(9*diffLsn)) {
				html += `warm3` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+(9*diffLsn)) && pages[i].PageLsn < (minLsn+(10*diffLsn)) {
				html += `warm4` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			} else if pages[i].PageLsn >= (minLsn+(10*diffLsn)) && pages[i].PageLsn <= maxLsn {
				html += `warm5` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
			}
		} else {
			html += `green` + `" data-content="page ` + strconv.Itoa(int(pages[i].PageOffset)) + ", " + pages[i].PageType + ", " + strconv.FormatInt(pages[i].PageLsn, 10) + `"></div>`
		}
	}
	html += ReadHtml("html/templates/pagemap_down.html")
	_ = os.Truncate("html/pagemap.html", 0)
	_ = ioutil.WriteFile(`html/pagemap.html`, []byte(html), 0666)
}

type FileHeader struct {
	PageSpace  uint
	PageOffset uint
	PagePrev   uint
	PageNext   uint
	PageLsn    int64
	PageType   uint16
	//PageFileFlushLsn int64
	//PageArchLogNo int
}

type PageHaeder struct {
	//PageNDirSlots
	//PageHeapTop
	PageNHeap uint16
	//PageFree
	//PageGarbage
	//PageLastInsert
	//PageDirection
	//PageNDirection
	PageNRecs uint16
	//PageMaxTrxId
	PageLevel uint16
	//PageIndexId
	//PageBtrSegLeaf
	//PageBtrSegTop
}

type Page struct {
	PageOffset uint
	PageLsn    int64
	PageType   string
	PageLevel  uint16
}

func readFileHeader(in []byte) FileHeader {
	var fh FileHeader
	fh.PageSpace = byte2Int32(in[0:4])
	fh.PageOffset = byte2Int32(in[4:8])
	fh.PagePrev = byte2Int32(in[8:12])
	fh.PageNext = byte2Int32(in[12:16])
	fh.PageLsn = byte2Int64(in[16:24])
	fh.PageType = byte2Int16(in[24:26])
	//fh.PageFileFlushLsn = byte2Int64(in[26:34])
	//fh.PageArchLogNo = byte2Int32(in[34:])
	return fh
}

func readPageHeader(in []byte) PageHaeder {
	var ph PageHaeder
	ph.PageNHeap = byte2Int16(in[4:6])
	ph.PageNRecs = byte2Int16(in[16:18])
	ph.PageLevel = byte2Int16(in[26:28])
	return ph
}

func byte2Int16(data []byte) uint16 {
	x := (int16(data[1]) & 255) | (int16(data[0]))<<8
	return uint16(x)
}

func byte2Int32(data []byte) uint {
	x := (int(data[3]) & 0xff) |
		(int(data[2])&0xff)<<8 |
		(int(data[1])&0xff)<<16 |
		(int(data[0])&0xff)<<24
	return uint(x)
}

func byte2Int64(data []byte) int64 {
	return (int64(data[7]) & 0xff) |
		(int64(data[6])&0xff)<<8 |
		(int64(data[5])&0xff)<<16 |
		(int64(data[4])&0xff)<<24 |
		(int64(data[3])&0xff)<<32 |
		(int64(data[2])&0xff)<<40 |
		(int64(data[1])&0xff)<<48 |
		(int64(data[0])&0xff)<<56
}

func checkFile(fSize int64, filePath string) bool {
	pages := fSize % PageSize
	if !(pages == 0) {
		fmt.Println("文件大小异常，请检查！")
		os.Exit(0)
	}
	return true
}

func ReadHtml(htmlName string) string {
	f, err := ioutil.ReadFile(htmlName)
	if err != nil {
		fmt.Println("read fail", err)
	}
	return string(f)
}
